#include <stdio.h>
#include <string.h>
#include "selector.h"
#include "flv.h"
#include "scan.h"
#include "opt.h"
#include "sub.h"

struct trackstat {
	unsigned long tag; /* number */
	long start, end; /* time */
};

struct audiostat {
	unsigned long total;
	unsigned long acodec[FLV_ACODEC_NUM];
	unsigned long arate[FLV_ARATE_NUM];
	unsigned long adepth[FLV_ADEPTH_NUM];
	unsigned long amode[FLV_AMODE_NUM];
};

struct videostat {
	unsigned long total;
	unsigned long vframe[FLV_VFRAME_NUM];
	unsigned long vcodec[FLV_VCODEC_NUM];
};

struct flvstat {
	struct trackstat track[FLV_TRACK_NUM + 1];
	struct audiostat audio[1];
	struct videostat video[1];
};

static unsigned check_bucket(const unsigned long *bkts,
				unsigned long bktnum,
				unsigned long samplenum)
{
	unsigned i;
	for (i = 0; i < bktnum; i++) {
		if (bkts[i] == 0)
			continue;
		if (bkts[i] == samplenum)
			return i;
		else
			break;
	}
	return bktnum;
}

static void print_aggregation(const unsigned long *bkts,
				const char *const *names,
				unsigned long bktnum, unsigned long samplenum)
{
	unsigned i, index;
	int first = 1;
	index = check_bucket(bkts, bktnum, samplenum);
	if (index != bktnum) {
		fputs(flvname(names, index), stdout);
		return;
	}
	for (i = 0; i < bktnum; i++) {
		if (bkts[i] == 0)
			continue;
		if (first) {
			printf("%s(%lu)", flvname(names, i), bkts[i]);
			first = 0;
		} else {
			printf(",%s(%lu)", flvname(names, i), bkts[i]);
		}
	}
}

#define PRINT_AGG(prefix, field, FIELD) \
index = check_bucket(s->field, FLV_##FIELD##_NUM, s->total);\
if (index != FLV_##FIELD##_DEF) {\
	fputs(prefix, stdout);\
	print_aggregation(s->field, flv_##field##_str, FLV_##FIELD##_NUM, s->total);\
}
static void print_audiostat(const struct audiostat *s)
{
	unsigned index;

	fputs(" codec ", stdout);
	print_aggregation(s->acodec, flv_acodec_str, FLV_ACODEC_NUM, s->total);

	PRINT_AGG(" rate ", arate, ARATE)
	PRINT_AGG(" depth ", adepth, ADEPTH)
	PRINT_AGG(" mode ", amode, AMODE)
}

static void print_videostat(const struct videostat *s)
{
	unsigned index;

	fputs(" codec ", stdout);
	print_aggregation(s->vcodec, flv_vcodec_str, FLV_VCODEC_NUM, s->total);

	PRINT_AGG(" frame ", vframe, VFRAME)
}
#undef PRINT_AGG

static void print_stat(const struct flvstat *s)
{
	unsigned i;
	const struct trackstat *t;
	unsigned tracks = 0;

	for (i = 0; i < FLV_TRACK_NUM; i++) {
		t = &s->track[i];
		if (t->tag == 0)
			continue;
		tracks++;

		fputs("track ", stdout);
		if (flv_track_str[i])
			fputs(flv_track_str[i], stdout);
		else
			printf("%u", i);
		printf(" time %.3f,%.3f tags %lu",
				t->start/1e3, t->end/1e3, t->tag);
		if (i == FLV_TRACK_AUDIO)
			print_audiostat(s->audio);
		else if (i == FLV_TRACK_VIDEO)
			print_videostat(s->video);
		putchar('\n');
	}
	t = &s->track[FLV_TRACK_ALL];
	printf("summary time %.3f,%.3f tags %lu gops %lu tracks %u\n",
		t->start/1e3, t->end/1e3, t->tag,
		s->video->vframe[FLV_VIDEO_KEY], tracks);
}

static void print_audiodata(const struct tagbuf *b)
{
	unsigned acodec, arate, adepth, amode;

	if (!b->len)
		return;

	acodec = flv_acodec(b);
	arate = flv_arate(b);
	adepth = flv_adepth(b);
	amode = flv_amode(b);

	if (flv_acodec_str[acodec])
		printf(" %s", flv_acodec_str[acodec]);
	else
		printf(" codec=%u", acodec);

	if (arate != FLV_ARATE_DEF)
		printf(" %s", flv_arate_str[arate]);

	if (adepth != FLV_ADEPTH_DEF)
		printf(" %s", flv_adepth_str[adepth]);

	if (amode != FLV_AMODE_DEF)
		printf(" %s", flv_amode_str[amode]);

	printf(" +%zu", b->len - FLV_AUDIOHDR_LEN);
}

static void print_videodata(const struct tagbuf *b)
{
	unsigned vframe, vcodec;

	if (!b->len)
		return;

	vframe = flv_vframe(b);
	vcodec = flv_vcodec(b);

	if (flv_vcodec_str[vcodec])
		printf(" %s", flv_vcodec_str[vcodec]);
	else
		printf(" codec=%u", vcodec);

	if (vframe != FLV_VFRAME_DEF) {
		if (flv_vframe_str[vframe])
			printf(" %s", flv_vframe_str[vframe]);
		else
			printf(" frame=%u", vframe);
	}

	printf(" +%zu", b->len - FLV_VIDEOHDR_LEN);
}

static void print_defaultdata(const struct tagbuf *b)
{
	printf(" +%zu", b->len);
}

static void print_tag(const struct tagbuf *b, const struct taginfo *i)
{
	printf("tag %lu %.3f ", i->ord, i->time/1e3);

	if (flv_track_str[i->track])
		fputs(flv_track_str[i->track], stdout);
	else
		printf("track=%u", i->track);

	switch (i->track) {
	case FLV_TRACK_AUDIO:
		print_audiodata(b);
		break;
	case FLV_TRACK_VIDEO:
		print_videodata(b);
		break;
	case FLV_TRACK_SCRIPT:
	default:
		print_defaultdata(b);
	}

	if (flv_stream(b) != 0)
		printf(" stream=%lu", flv_stream(b));

	if (flv_pts(b) != b->len + FLV_TAGHDR_LEN)
		printf(" pts=%lu", flv_pts(b));

	putchar('\n');
}

static void print_gop(const struct gopinfo *i)
{
	printf("gop %lu tag %lu,%lu time %.3f,%.3f\n", i->ord,
			i->first.ord, i->last.ord,
			i->first.time/1e3, i->last.time/1e3);
}

static void print_hdr(const struct tagbuf *b)
{
	fputs("hdr", stdout);
	if (flv_version(b) != FLV_VERSION_DEF)
		printf(" version %u", flv_version(b));
	if (flv_aflag(b) != FLV_AFLAG_DEF)
		fputs(" noaudio", stdout);
	if (flv_vflag(b) != FLV_VFLAG_DEF)
		fputs(" novideo", stdout);
	if (flv_res1(b))
		printf(" res1=%u", flv_res1(b));
	if (flv_res2(b))
		printf(" res2=%u", flv_res2(b));
	if (b->len > 0)
		printf(" +%zu", b->len);
	if (flv_pts(b) != 0)
		printf(" pts=%lu", flv_pts(b));
	putchar('\n');
}

static void do_trackstat(const struct tagbuf *b, struct trackstat *s)
{
	int time;
	time = flv_time(b);
	s->tag++;
	if (time < s->start || s->tag == 1)
		s->start = time;
	if (time > s->end || s->tag == 1)
		s->end = time;
}

static void do_audiostat(const struct tagbuf *b, struct audiostat *s)
{
	s->total++;
	s->acodec[flv_acodec(b)]++;
	s->arate[flv_arate(b)]++;
	s->adepth[flv_adepth(b)]++;
	s->amode[flv_amode(b)]++;
}

static void do_videostat(const struct tagbuf *b, struct videostat *s)
{
	s->total++;
	s->vframe[flv_vframe(b)]++;
	s->vcodec[flv_vcodec(b)]++;
}

static void do_flvstat(const struct tagbuf *b, struct flvstat *s)
{
	unsigned char track;

	track = flv_track(b);

	do_trackstat(b, &s->track[track]);
	do_trackstat(b, &s->track[FLV_TRACK_ALL]);

	if (track == FLV_TRACK_AUDIO)
		do_audiostat(b, s->audio);
	else if (track == FLV_TRACK_VIDEO)
		do_videostat(b, s->video);
}

static int target_gop;

static scan_verdict
callback(scan_phase phase, struct tagbuf *b, struct context *ctx)
{
	static struct flvstat stat[1];

	switch (phase) {
	case BEFORE:
		memset(stat, 0, sizeof(*stat));
		break;

	case HDR:
		if (!target_gop)
			print_hdr(b);
		break;

	case TAG:
		if (!is_selected(ctx))
			break;

		do_flvstat(b, stat);

		if (!target_gop)
			print_tag(b, &ctx->tag[0]);
		else if (ctx->gop[0].first.ord == ctx->gop[0].last.ord &&
		ctx->gop[1].last.ord > 0 /* not empty */)
			print_gop(&ctx->gop[1]);

		break;

	case AFTER:
		if (target_gop && ctx->gop[0].last.ord > 0 && is_selected(ctx))
			print_gop(&ctx->gop[0]);
		print_stat(stat);
		break;
	default:
		break;
	}

	return DISCARD;
}

static int show_help;
static const struct option_def optdef[] = {
	{OPT_FLAG, 'g', "gop", {.flag = &target_gop}, "gop mode", 0},
	{OPT_FLAG, 0, "help", {.flag = &show_help}, "show help", 0},
	{.type = OPT_GROUP, .ptr = {.group = &selector_options}},
	{.type = OPT_END},
};
static const struct option_group optgroup = {.defs = optdef};

static void usage(void)
{
	printf("Usage: %s [-g] [Selectors] [FILE]\n", prog);
}

int ls_main(int argc, const char *const *argv)
{
	const char *file = 0;
	FILE *in;
	int r;

	if (parse_options(argc, argv, &optgroup, 1, &file) < 0) {
		usage();
		return 2;
	}
	if (show_help) {
		usage();
		print_options(&optgroup);
		return 0;
	}

	in = open_file(file, 'r');
	if (!in)
		return 1;

	r = scan(in, 0, callback);

	close_file(in);

	return r;
}
